/***********************************************************************

	This file is part of KEEL-software, the Data Mining tool for regression, 
	classification, clustering, pattern mining and so on.

	Copyright (C) 2004-2010
	
	F. Herrera (herrera@decsai.ugr.es)
    L. Sánchez (luciano@uniovi.es)
    J. Alcalá-Fdez (jalcala@decsai.ugr.es)
    S. García (sglopez@ujaen.es)
    A. Fernández (alberto.fernandez@ujaen.es)
    J. Luengo (julianlm@decsai.ugr.es)

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see http://www.gnu.org/licenses/
  
**********************************************************************/

/*
 *    FURIA.java
 *    Copyright (C) 2008 Jens Christian Hühn
 *    
 *    (based upon) JRip.java
 *    Copyright (C) 2001 Xin Xu, Eibe Frank
 */

package keel.Algorithms.Fuzzy_Rule_Learning.Hybrid.FURIA;

import java.io.Serializable;
import java.util.Enumeration;
import java.util.Vector;
import org.core.Files;
import org.core.Randomize;
import keel.Algorithms.Fuzzy_Rule_Learning.Hybrid.FURIA.core.*;
import keel.Dataset.Attribute;
import keel.Dataset.Attributes;
import keel.Dataset.InstanceSet;


/**
 <!-- globalinfo-start -->
 * This class implements the FURIA algorithm proposed by Hühn and Hüllermeier 2009<br/>
 * <br/>
 * The FURIA algorithm is a fuzzy rule learner based on the JRip implementation (RIPRER). The main difference between FURIA and JRip is that FURIA makes no use of default rules. Furthermore FURIA has a changed pruning procedure, which means that the pruning during the IREP* runs was deactivated permanently. It was found out experimentally that this improved the classification rate. The following description from the JRip class was altered to describe the methodology of FURIA: <br/>
 * <br/>
 * Initialize RS = {}, and for each of both class DO: <br/>
 * <br/>
 * 1. Building stage:<br/>
 * Repeat 1.1 until the description length (DL) of the ruleset and examples is 64 bits greater than the smallest DL met so far, or there are no positive examples, or the error rate >= 50%. <br/>
 * <br/>
 * 1.1. Grow phase:<br/>
 * Grow one rule by greedily adding antecedents (or conditions) to the rule until the rule is perfect (i.e. 100% accurate).  The procedure tries every possible value of each attribute and selects the condition with highest information gain: p(log(p/t)-log(P/T)).<br/>
 * <br/>
 * 2. Optimization stage:<br/>
 *  after generating the initial ruleset {Ri}, generate and prune two variants of each rule Ri from randomized data using procedure 1.1 and X.1. But one variant is generated from an empty rule while the other is generated by greedily adding antecedents to the original rule. Moreover, the pruning metric used here is (TP+TN)/(P+N).Then the smallest possible DL for each variant and the original rule is computed.  The variant with the minimal DL is selected as the final representative of Ri in the ruleset.After all the rules in {Ri} have been examined and if there are still residual positives, more rules are generated based on the residual positives using Building Stage again. <br/>
 * 3. Delete the rules from the ruleset that would increase the DL of the whole ruleset if it were in it. and add resultant ruleset to RS. <br/>
 * ENDDO<br/>
 * <br/>
 * Fuzzification of RS:<br/>
 * For each rule r in every ruleset in RS DO<br/>
 * 4. Fuzzification of antecedents: <br/>
 * Apply greedy strategy to fuzzify the existing antecedents in r the following way:<br/>
 * <br/>
 * 4.1 Examine all possible support bounds and select the one which gains the highest purity on the training data.<br/>
 * 4.2 Set the maximum support bound determined in 4.1 and restart with 4.1 but withouth the fuzzified antecedent.<br/>
 * ENDDO<br/>
 * <br/>
 * X.1. Pruning:<br/>
 * Incrementally prune each rule and allow the pruning of any final sequences of the antecedents;The pruning metric is (p-n)/(p+n) -- but it's actually 2p/(p+n) -1, so in this implementation we simply use p/(p+n) (actually (p+1)/(p+n+2), thus if p+n is 0, it's 0.5).
 * <br><br>
 * Classification time:<br>
 * If an instance is not covered by any rule, apply a rule stretching mechanism: Cut every rule just in front of the first discriminating antecedent such that the this way stretched rule covers the instance. Doing this for all rules will lead to a set of rules in which each one covers the instance (or is empty and may be excluded). To determine the rule that assigns the class calculate the weight given by its purity using the m-measure on the one hand the Laplace-fraction of antecedents left in comparison to the original number of the respective rule. The rule that maximizes that value is from the class that will be assigned.
 * <p/>
 <!-- globalinfo-end -->
 *
 <!-- technical-bibtex-start -->
 * BibTeX:
 * <pre>
 *  &#64;article{huehn2009furia,
 *    author = {Jens Christian Hühn and Eyke Hüllermeier},
 *    journal = {Data Mining and Knowledge Discovery},
 *    title = {FURIA: An Algorithm for Unordered Fuzzy Rule Induction},
 *    year = {to appear}
 * }
 * &#64;inproceedings{Cohen1995,
 *    author = {William W. Cohen},
 *    booktitle = {Twelfth International Conference on Machine Learning},
 *    pages = {115-123},
 *    publisher = {Morgan Kaufmann},
 *    title = {Fast Effective Rule Induction},
 *    year = {1995}
 * }
 * </pre>
 * <p/>
 *  <!-- technical-bibtex-end -->
 *
 <!-- options-start -->
 * Valid options are: <p/>
 * 
 * <pre> -F &lt;number of folds&gt;
 *  Set number of folds for REP
 *  One fold is used as pruning set.
 *  (default 3)</pre>
 * 
 * <pre> -N &lt;min. weights&gt;
 *  Set the minimal weights of instances
 *  within a split.
 *  (default 2.0)</pre>
 * 
 * <pre> -O &lt;number of runs&gt;
 *  Set the number of runs of
 *  optimizations. (Default: 2)</pre>
 * 
 * <pre> -D
 *  Set whether turn on the
 *  debug mode (Default: false)</pre>
 * 
 * <pre> -S &lt;seed&gt;
 *  The seed of randomization
 *  (Default: 1)</pre>
 * 
 * <pre> -E
 *  Whether NOT check the error rate&gt;=0.5
 *  in stopping criteria  (default: check)</pre>
 *   
 * <pre> -s
 *  Whether use rule stretching or refrain from classifying
 *  (default: use stretching)</pre>
 * 
 <!-- options-end -->
 *
 * @author written by Jens Christian Hühn (huehn@gmx.net)
 * @author modified for KEEL by Alberto Fernández (alberto.fernandez@ujaen.es) [University of Jaen] 23/10/2013
 * @version $Revision: 1.1 $
 */
public class FURIA extends Classifier implements WeightedInstancesHandler {    

	/** for serialization */
	static final long serialVersionUID = -6589312996832147161L;

	/** The limit of description length surplus in ruleset generation */
	private static double MAX_DL_SURPLUS = 64.0;

	/** The class attribute of the data*/
	protected AttributeWeka m_Class; 

	/** The ruleset */
	public FastVector m_Ruleset;

	/** The predicted class distribution */
	protected FastVector m_Distributions;

	/** Runs of optimizations */
	private int m_Optimizations;

	/** Random object used in this class */
	protected Randomize m_Random = null;

	/** # of all the possible conditions in a rule */
	protected double m_Total = 0;

	/** The seed to perform randomization */
	protected long m_Seed;

	/** The number of folds to split data into Grow and Prune for IREP */
	private int m_Folds;
	/** The minimal number of instance weights within a split*/
	double m_MinNo = 2.0;

	/** Whether in a debug mode */
	protected boolean m_Debug = false;

	/** Whether check the error rate >= 0.5 in stopping criteria */
	private boolean m_CheckErr = true;

	/** The class distribution of the training data*/
	double[] aprioriDistribution;

	/** The RuleStats for the ruleset of each class value */
	protected FastVector m_RulesetStats;

	/** Whether use rule stretching */
	private boolean m_useRuleStretching = true;


	//--------------------------------------------------------------------------------------------------------
	//--------------------------------------------------------------------------------------------------------
	//--------------------------------------------------------------------------------------------------------

	/**
	 * Constructor.
	 *
	 * @param params The parameters of the algorithm
	 */
	private String trainFile, evalFile, testFile;
	private String outputTrainFile, outputTestFile, outputClassifierFile;
	/** filter: Normalize training data */
	public static final int FILTER_NORMALIZE = 0;
	/** filter: Standardize training data */
	public static final int FILTER_STANDARDIZE = 1;
	/** filter: No normalization/standardization */
	public static final int FILTER_NONE = 2;

    /**
     * Constructor. Build the FURIA object and parse all parameters with the parser given.
     * @param params given parser.
     */
    public FURIA(parseParameters params) {
		m_Seed = Long.parseLong(params.getParameter(0));
		m_Optimizations = Integer.parseInt(params.getParameter(1));//2
		m_Folds = Integer.parseInt(params.getParameter(2));//3

		trainFile = params.getTrainingInputFile();
		evalFile = params.getValidationInputFile();
		testFile = params.getTestInputFile();
		outputTrainFile = params.getTrainingOutputFile();
		outputTestFile = params.getTestOutputFile();
		outputClassifierFile = params.getOutputFile(0);
	}

	/**
	 * It launches the FURIA algorithm
	 */
	public void execute() {
		Instances isWeka;
		Instance instWeka;
		InstanceSet IS = new InstanceSet();
		InstanceSet ISval = new InstanceSet();
		InstanceSet IStest = new InstanceSet();

		try {
			//*********build the FR3 classifier********/
			IS.readSet(trainFile, true);
			isWeka = InstancesKEEL2Weka(IS, FILTER_NONE);
			buildClassifier(isWeka);

			//********validate the obtained FR3*******//

			ISval.readSet(evalFile, false);
			isWeka = InstancesKEEL2Weka(ISval, FILTER_NONE);
			//obtain the predicted class for each train instance

			Attribute a = Attributes.getOutputAttribute(0);
			String outputVal = new String("");
			int hits = 0;
			for (int i = 0; i < isWeka.numInstances(); i++) {
				keel.Dataset.Instance inst = ISval.getInstance(i);
				instWeka = isWeka.instance(i);
				instWeka.setDataset(isWeka);
				int outputClass = (int)this.classifyInstance(instWeka);
				String realClass = inst.getOutputNominalValues(0);
				String predictedClass = a.getNominalValue(outputClass);
				if (realClass.compareTo(predictedClass) == 0) {
					hits++;
				}
				outputVal += realClass + " " + predictedClass+"\n";
			}
			double accTrain = 1.0 * hits / isWeka.numInstances();

			IStest.readSet(testFile, false);
			isWeka = InstancesKEEL2Weka(IStest, FILTER_NONE);
			String outputTest = new String("");
			hits = 0;
			for (int i = 0; i < isWeka.numInstances(); i++) {
				keel.Dataset.Instance inst = IStest.getInstance(i);
				instWeka = isWeka.instance(i);
				instWeka.setDataset(isWeka);
				int outputClass = (int)this.classifyInstance(instWeka);
				String realClass = inst.getOutputNominalValues(0);
				String predictedClass = a.getNominalValue(outputClass);
				if (realClass.compareTo(predictedClass) == 0) {
					hits++;
				}
				outputTest += realClass + " " + predictedClass+"\n";
			}
			double accTest = 1.0 * hits / isWeka.numInstances();
			writeOutput(outputVal,outputTest,accTrain,accTest);

		} catch (Exception ex) {
			System.err.println("Fatal Error building the FURIA model!");
			ex.printStackTrace();
		}
		;

		Files.writeFile(outputClassifierFile, toString() + "\n\n\n\n" + "REGLAS = " + m_Ruleset.size());
	}

	/**
	 * Creates a new allocated WEKA's set of Instances (i.e. Instances) from a KEEL's set of instances
	 * (i.e. InstanceSet).
	 * @param is The KEEL Instance set
	 * @param preprocessType An integer with the type of preprocess done before exporting data to Weka format (0 = normalize, 1 = standardize, 2 = do nothing).
	 * @return A new allocated WEKA formatted Instance set
	 */
	protected Instances InstancesKEEL2Weka(InstanceSet is, int preprocessType) {
		Attribute a, newAt;
		Instance instW;
		keel.Dataset.Instance instK;
		int out, in, newNumAttributes, enlargedValueVectorPos;
		double values[];
		Instances data;
		FastVector atts;

		// Create header of instances object
		out = Attributes.getInputNumAttributes(); //the class attribute is usually the last one
		atts = new FastVector(Attributes.getNumAttributes());
		for (int i = 0; i < Attributes.getNumAttributes(); i++) {
			a = Attributes.getAttribute(i);
			//atts.addElement(a);
			AttributeWeka aWeka;
			if (a.getType() == a.NOMINAL) {
				FastVector nominalValues = new FastVector(a.getNumNominalValues());
				for (int j = 0; j < a.getNumNominalValues(); j++) {
					nominalValues.addElement(a.getNominalValue(j));
				}
				aWeka = new AttributeWeka(a.getName(), nominalValues, i);
			} else {
				String range = new String("[");
				range += a.getMinAttribute();
				range += ",";
				range += a.getMaxAttribute();
				range += ")";
				aWeka = new AttributeWeka(a.getName(), i);
				aWeka.setNumericRange(range);
			}
			atts.addElement(aWeka);
			if (a.getDirectionAttribute() == Attribute.OUTPUT) {
				out = i;
			}
		}
		data = new Instances(Attributes.getRelationName(), atts,
				is.getNumInstances());
		data.setClassIndex(out);
		newNumAttributes = Attributes.getNumAttributes();

		//now fill the data in the data instanceset
		for (int i = 0; i < is.getNumInstances(); i++) {
			instK = is.getInstance(i);
			in = out = 0;
			enlargedValueVectorPos = 0;
			values = new double[newNumAttributes];
			for (int j = 0; j < Attributes.getNumAttributes(); j++) {
				a = Attributes.getAttribute(j);
				if (a.getDirectionAttribute() == Attribute.INPUT) {
					if (a.getType() != Attribute.NOMINAL) {
						values[enlargedValueVectorPos] = instK.
						getAllInputValues()[in];
						enlargedValueVectorPos++;
					} else {
						values[enlargedValueVectorPos] = instK.
						getAllInputValues()[in];
						enlargedValueVectorPos++;
					}
					in++;
				} else {
					values[enlargedValueVectorPos] = instK.getAllOutputValues()[
					                                                            out];
					out++;
					enlargedValueVectorPos++;
				}
			}
			//**IMPORTANT** We set the weight of the instance to ONE
			instW = new Instance(1, values);
			data.add(instW);
		}

		return data;
	}

	/**
	 * It writes the training and test files with the real and predicted classes
	 * @param outputVal String The string with the training (validation) output
	 * @param outputTest String The string with the test output
	 * @param accTrain double The accuracy rate in training
	 * @param accTest double The accuracy rate in test
	 */
	void writeOutput(String outputVal, String outputTest, double accTrain, double accTest){
		String p = new String("");
		p = "@relation " + Attributes.getRelationName() + "\n";
		p += Attributes.getInputAttributesHeader();
		p += Attributes.getOutputAttributesHeader();
		p += Attributes.getInputHeader() + "\n";
		p += Attributes.getOutputHeader() + "\n";
		p += "@data\n";
		Files.writeFile(outputTrainFile,p+outputVal);
		Files.writeFile(outputTestFile,p+outputTest);
		System.out.println("Training accuracy: "+accTrain);
		System.out.println("Test accuracy: "+accTest);
		System.out.println("m_Optimizations = " + m_Optimizations);
		System.out.println("m_Seed = " + m_Seed);
		System.out.println("m_Folds = " + m_Folds);
		System.out.println("m_MinNo = " + m_MinNo);
	}


	//--------------------------------------------------------------------------------------------------------
	//--------------------------------------------------------------------------------------------------------
	//--------------------------------------------------------------------------------------------------------

	/**
	 * Returns an enumeration of the additional measure names
	 * @return an enumeration of the measure names
	 */
	public Enumeration enumerateMeasures() {
		Vector newVector = new Vector(1);
		newVector.addElement("measureNumRules");
		return newVector.elements();
	}

	/**
	 * Returns the value of the named measure
	 * @param additionalMeasureName the name of the measure to query for its value
	 * @return the value of the named measure
	 * @throws IllegalArgumentException if the named measure is not supported
	 */
	public double getMeasure(String additionalMeasureName) {
		if (additionalMeasureName.compareToIgnoreCase("measureNumRules") == 0) 
			return m_Ruleset.size();
		else 
			throw new IllegalArgumentException(additionalMeasureName+" not supported (RIPPER)");
	}  

	/**
	 * Returns the tip text for this property
	 * @return tip text for this property suitable for
	 * displaying in the explorer/experimenter gui
	 */
	public String foldsTipText() {
		return "Determines the amount of data used for pruning. One fold is used for "
		+ "pruning, the rest for growing the rules.";
	}

	/**
	 * Sets the number of folds to use
	 * 
	 * @param fold the number of folds
	 */
	public void setFolds(int fold) { 
		m_Folds = fold; 
	}

	/**
	 * Gets the number of folds
	 * 
	 * @return the number of folds
	 */
	public int getFolds(){ 
		return m_Folds; 
	}

	/**
	 * Returns the tip text for this property
	 * @return tip text for this property suitable for
	 * displaying in the explorer/experimenter gui
	 */
	public String minNoTipText() {
		return "The minimum total weight of the instances in a rule.";
	}

	/**
	 * Sets the minimum total weight of the instances in a rule
	 * 
	 * @param m the minimum total weight of the instances in a rule
	 */
	public void setMinNo(double m) {
		m_MinNo = m;
	}

	/**
	 * Gets the minimum total weight of the instances in a rule
	 * 
	 * @return the minimum total weight of the instances in a rule
	 */
	public double getMinNo(){ 
		return m_MinNo;
	}

	/**
	 * Returns the tip text for this property
	 * @return tip text for this property suitable for
	 * displaying in the explorer/experimenter gui
	 */
	public String seedTipText() {
		return "The seed used for randomizing the data.";
	}

	/**
	 * Sets the seed value to use in randomizing the data
	 * 
	 * @param s the new seed value
	 */
	public void setSeed(long s) {
		m_Seed = s;
	}

	/**
	 * Gets the current seed value to use in randomizing the data
	 * 
	 * @return the seed value
	 */
	public long getSeed(){
		return m_Seed;
	}

	/**
	 * Returns the tip text for this property
	 * @return tip text for this property suitable for
	 * displaying in the explorer/experimenter gui
	 */
	public String optimizationsTipText() {
		return "The number of optimization runs.";
	}

	/**
	 * Sets the number of optimization runs
	 * 
	 * @param run the number of optimization runs
	 */
	public void setOptimizations(int run) {
		m_Optimizations = run;
	}

	/**
	 * Gets the the number of optimization runs
	 * 
	 * @return the number of optimization runs
	 */
	public int getOptimizations() {
		return m_Optimizations;
	}

	/**
	 * Returns the tip text for this property
	 * @return tip text for this property suitable for
	 * displaying in the explorer/experimenter gui
	 */
	public String debugTipText() {
		return "Whether debug information is output to the console.";
	}

	/**
	 * Sets whether debug information is output to the console
	 * 
	 * @param d whether debug information is output to the console
	 */
	public void setDebug(boolean d) {
		m_Debug = d;
	}

	/**
	 * Gets whether debug information is output to the console
	 * 
	 * @return whether debug information is output to the console
	 */
	public boolean getDebug(){
		return m_Debug;
	}

	/**
	 * Returns the tip text for this property
	 * @return tip text for this property suitable for
	 * displaying in the explorer/experimenter gui
	 */
	public String checkErrorRateTipText() {
		return "Whether check for error rate >= 1/2 is included" +
		" in stopping criterion.";
	}

	/**
	 * Sets whether to check for error rate is in stopping criterion
	 * 
	 * @param d whether to check for error rate is in stopping criterion
	 */
	public void setCheckErrorRate(boolean d) { 
		m_CheckErr = d;
	}

	/**
	 * Gets whether to check for error rate is in stopping criterion
	 * 
	 * @return true if checking for error rate is in stopping criterion
	 */
	public boolean getCheckErrorRate(){ 
		return m_CheckErr; 
	} 

	/**
	 * Returns the tip text for this property
	 * @return tip text for this property suitable for
	 * displaying in the explorer/experimenter gui
	 */
	public String useRuleStretchingTipText() {
		return "Whether rule stretching is performed.";
	}

	/**
	 * Sets whether pruning is performed
	 * 
	 * @param d Whether pruning is performed
	 */
	public void setUseRuleStretching(boolean d) { 
		m_useRuleStretching = d;
	}

	/**
	 * Gets whether pruning is performed
	 * 
	 * @return true if pruning is performed
	 */
	public boolean getUseRuleStretching(){ 
		return m_useRuleStretching; 
	}

	/** 
	 * Get the ruleset generated by Ripper 
	 *
	 * @return the ruleset
	 */
	public FastVector getRuleset(){ return m_Ruleset; }

	/** 
	 * Get the statistics of the ruleset in the given position
	 *
	 * @param pos the position of the stats, assuming correct
	 * @return the statistics of the ruleset in the given position
	 */
	public RuleStats getRuleStats(int pos) {
		return (RuleStats)m_RulesetStats.elementAt(pos);
	}

	/**
	 * Builds Ripper in the order of class frequencies.  For each class
	 * it's built in two stages: building and optimization 
	 *
	 * @param instances the training data
	 * @throws Exception if classifier can't be built successfully
	 */
	public void buildClassifier(Instances instances) throws Exception {  

		// can classifier handle the data?
		//getCapabilities().testWithFail(instances);

		// remove instances with missing class
		instances = new Instances(instances);
		instances.deleteWithMissingClass();

		aprioriDistribution = new double[instances.classAttribute().numValues()];
		boolean allWeightsAreOne = true;
		for (int i = 0 ; i < instances.numInstances(); i++){
			aprioriDistribution[(int)instances.instance(i).classValue()]+=instances.instance(i).weight();
			if (allWeightsAreOne && instances.instance(i).weight() != 1.0){
				allWeightsAreOne = false;
				break;
			}
		}

		m_Random.setSeed(m_Seed);//m_Random = instances.getRandomNumberGenerator(m_Seed);

		m_Total = RuleStats.numAllConditions(instances);
		if(m_Debug)
			System.err.println("Number of all possible conditions = "+m_Total);

		Instances data = new Instances(instances);

		if(data == null)
			throw new Exception(" Unable to randomize the class orders.");

		m_Class = data.classAttribute();	
		m_Ruleset = new FastVector();
		m_RulesetStats = new FastVector();
		m_Distributions = new FastVector();

		// Sort by classes frequency    
		if(m_Debug){
			System.err.println("Sorted classes:");
			for(int x=0; x < m_Class.numValues(); x++)
				System.err.println(x+": "+m_Class.value(x) + " has " +
						aprioriDistribution[x] + " instances.");
		}
		// Iterate from less prevalent class to more frequent one
		oneClass:	
			for(int y=0; y < data.numClasses(); y++){ // For each class	      

				double classIndex = (double)y;
				if(m_Debug){
					int ci = (int)classIndex;
					System.err.println("\n\nClass "+m_Class.value(ci)+"("+ci+"): "
							+ aprioriDistribution[y] + "instances\n"+
					"=====================================\n");
				}

				if(Utils.eq(aprioriDistribution[y],0.0)) // No data for this class
					continue oneClass;		

				// The expected FP/err is the proportion of the class    
				double expFPRate = (aprioriDistribution[y] / Utils.sum(aprioriDistribution));


				double classYWeights = 0, totalWeights = 0;
				for(int j=0; j < data.numInstances(); j++){
					Instance datum = data.instance(j);
					totalWeights += datum.weight();
					if((int)datum.classValue() == y){
						classYWeights += datum.weight();
					}	          
				}	

				// DL of default rule, no theory DL, only data DL
				double defDL;
				if(classYWeights > 0)
					defDL = RuleStats.dataDL(expFPRate, 
							0.0,
							totalWeights,
							0.0,
							classYWeights);	    
				else
					continue oneClass; // Subsumed by previous rules



				if(Double.isNaN(defDL) || Double.isInfinite(defDL))
					throw new Exception("Should never happen: "+
					"defDL NaN or infinite!");
				if(m_Debug)
					System.err.println("The default DL = "+defDL);

				rulesetForOneClass(expFPRate, data, classIndex, defDL);
			}

		for(int z=0; z < m_Ruleset.size(); z++){
			RipperRule rule = (RipperRule)m_Ruleset.elementAt(z);
			for(int j = 0; j < rule.m_Antds.size(); j++){
				Antd outerAntd = (Antd)rule.m_Antds.elementAt(j);
				for (int k = j+1; k < rule.m_Antds.size(); k++){
					Antd innerAntd = (Antd)rule.m_Antds.elementAt(k);  
					if (outerAntd.att.index() == innerAntd.att.index() && outerAntd.value==innerAntd.value){
						rule.m_Antds.setElementAt(rule.m_Antds.elementAt(k), j);
						rule.m_Antds.removeElementAt(k--);
					}
				}
			}
		}


		for(int z=0; z < m_RulesetStats.size(); z++){
			RuleStats oneClass = (RuleStats)m_RulesetStats.elementAt(z);
			for(int xyz=0; xyz < oneClass.getRulesetSize(); xyz++){
				RipperRule rule = (RipperRule)((FastVector)oneClass.getRuleset()).elementAt(xyz);

				// 1st, the fuzzification for all known antecedents is done
				rule.findAndSetSupportBoundForKnownAntecedents(data, allWeightsAreOne);

				double[] classDist = oneClass.getDistributions(xyz);
				// Check for sum=0, because otherwise it does not work
				if (Utils.sum(classDist)>0) Utils.normalize(classDist);
				if(classDist != null)
					m_Distributions.addElement(classDist);
			}	
		}

		for(int z=0; z < m_Ruleset.size(); z++){
			RipperRule rule = (RipperRule)m_Ruleset.elementAt(z);
			for(int j = 0; j < rule.m_Antds.size(); j++){
				Antd antd = (Antd)rule.m_Antds.elementAt(j);
				if (antd instanceof NumericAntd) {
					NumericAntd numAntd = (NumericAntd) antd;


					if (!numAntd.fuzzyYet){
						for (int i = 0; i < data.numInstances(); i++){
							if ((numAntd.value == 1 && 
									numAntd.splitPoint > data.instance(i).value(numAntd.att.index()) &&
									(numAntd.supportBound < data.instance(i).value(numAntd.att.index()) ||
											!numAntd.fuzzyYet)
							)
							||
							(numAntd.value == 0 && 
									numAntd.splitPoint < data.instance(i).value(numAntd.att.index()) &&
									(numAntd.supportBound > data.instance(i).value(numAntd.att.index()) ||
											!numAntd.fuzzyYet)
							)
							){
								numAntd.supportBound = data.instance(i).value(numAntd.att.index());
								numAntd.fuzzyYet = true;
							}
						}

					}	  
				}
			}
		}

		//Determine confidence
		for(int z=0; z < m_Ruleset.size(); z++){
			RipperRule rule = (RipperRule)m_Ruleset.elementAt(z);
			rule.calculateConfidences(data);
		}   
	}

	/**
	 * Classify the test instance with the rule learner and provide
	 * the class distributions 
	 *
	 * @param datum the instance to be classified
	 * @return the distribution
	 * @throws Exception 
	 */

	public double[] distributionForInstance(Instance datum) throws Exception{ 
		//test for multiple overlap of rules
		double[] rulesCoveringForEachClass = new double[datum.numClasses()];  
		for(int i=0; i < m_Ruleset.size(); i++){
			RipperRule rule = (RipperRule)m_Ruleset.elementAt(i);

			/* In case that one class does not contain any instances (e.g. in UCI-dataset glass), 
			 * a default rule assigns all instances to the other class. Such a rule may be ignored here.
			 */
			if (!rule.hasAntds()) 
				continue;


			// Calculate the maximum degree of coverage
			if(rule.covers(datum)){
				rulesCoveringForEachClass[(int)rule.m_Consequent] += rule.coverageDegree(datum) * rule.getConfidence();
				//System.err.println("Cov: "+rule.getConfidence());
			}

		}


		// If no rule covered the example, then maybe start the rule stretching
		if (Utils.sum(rulesCoveringForEachClass)==0){

			// If rule stretching is not allowed, return no prediction
			if (m_useRuleStretching == false)
				return new double[rulesCoveringForEachClass.length];

			// Copy the ruleset as backup
			FastVector origRuleset = (FastVector) m_Ruleset.copyElements();

			// Find for every rule the first antecedent that does not
			// cover the given instance. 
			double maxConfidence = Double.NEGATIVE_INFINITY;
			for(int i=0; i < m_Ruleset.size(); i++){
				RipperRule rule = (RipperRule)m_Ruleset.elementAt(i);
				double numAntdsBefore = rule.m_Antds.size();

				int firstAntdToDelete = Integer.MAX_VALUE;
				for (int j = 0; j < rule.m_Antds.size(); j++){
					if (((Antd)rule.m_Antds.elementAt(j)).covers(datum)==0){
						firstAntdToDelete = j;
						break;
					}
				}

				// Prune antecedent such that it covers the instance
				for (int j = firstAntdToDelete; j < rule.m_Antds.size(); j++){
					rule.m_Antds.removeElementAt(j--);
				}
				double numAntdsAfter = rule.m_Antds.size();

				// Empty rules shall not vote here
				if (!rule.hasAntds())
					continue;

				// Calculate the maximum degree of coverage and weight the rule
				// by its confidence and the fraction of antecedents left after
				// rule stretching
				double secondWeight = (numAntdsAfter+1)/(numAntdsBefore+2) ;
				if (rule.getConfidence() *secondWeight*rule.coverageDegree(datum) >= maxConfidence){
					maxConfidence = rule.getConfidence()*secondWeight*rule.coverageDegree(datum);
					rulesCoveringForEachClass = new double[rulesCoveringForEachClass.length];
					rulesCoveringForEachClass[(int)rule.m_Consequent] = 1;
				}      
			}

			// Reestablish original ruleset
			m_Ruleset = origRuleset;
		}

		//check for conflicts
		double[] maxClasses = new double[rulesCoveringForEachClass.length];
		for (int i = 0; i < rulesCoveringForEachClass.length; i++){
			if (rulesCoveringForEachClass[Utils.maxIndex(rulesCoveringForEachClass)] ==
				rulesCoveringForEachClass[i] && rulesCoveringForEachClass[i]>0)
				maxClasses[i] = 1;
		}

		if (Utils.sum(maxClasses)>0){
			for (int i = 0; i < maxClasses.length; i++){
				maxClasses[i] *= aprioriDistribution[i]/Utils.sum(aprioriDistribution);
			}
			rulesCoveringForEachClass=maxClasses;
		}


		// If no stretched rule was able to cover the instance,
		// then fall back to the apriori distribution
		if (Utils.sum(rulesCoveringForEachClass)==0){
			rulesCoveringForEachClass = aprioriDistribution;
		}

		if (Utils.sum(rulesCoveringForEachClass)>0)
			Utils.normalize(rulesCoveringForEachClass);

		return rulesCoveringForEachClass;

	}


	/** Build a ruleset for the given class according to the given data
	 *
	 * @param expFPRate the expected FP/(FP+FN) used in DL calculation
	 * @param data the given data
	 * @param classIndex the given class index
	 * @param defDL the default DL in the data
     * @return ruleset for the given class according to the given data
	 * @throws Exception if the ruleset can be built properly
	 */
	protected Instances rulesetForOneClass(double expFPRate, 
			Instances data, 
			double classIndex,
			double defDL)
	throws Exception {

		Instances newData = data, growData, pruneData;  	
		boolean stop = false;
		FastVector ruleset = new FastVector();		

		double dl = defDL, minDL = defDL;
		RuleStats rstats = null;
		double[] rst;

		// Check whether data have positive examples
		boolean defHasPositive = true; // No longer used
		boolean hasPositive = defHasPositive;

		/********************** Building stage ***********************/	
		if(m_Debug)
			System.err.println("\n*** Building stage ***");


		while((!stop) && hasPositive){ // Generate new rules until
			// stopping criteria met
			RipperRule oneRule;

			oneRule = new RipperRule(this.aprioriDistribution);
			oneRule.setConsequent(classIndex);  // Must set first
			if(m_Debug)
				System.err.println("\nNo pruning: growing a rule ...");
			oneRule.grow(newData);             // Build the rule
			if(m_Debug)
				System.err.println("No pruning: one rule found:\n"+
						oneRule.toString(m_Class));


			// Compute the DL of this ruleset
			if(rstats == null){ // First rule
				rstats = new RuleStats();
				rstats.setNumAllConds(m_Total);
				rstats.setData(newData);
			}

			rstats.addAndUpdate(oneRule);		    
			int last = rstats.getRuleset().size()-1; // Index of last rule
			dl += rstats.relativeDL(last, expFPRate, m_CheckErr);

			if(Double.isNaN(dl) || Double.isInfinite(dl))
				throw new Exception("Should never happen: dl in "+
				"building stage NaN or infinite!");
			if(m_Debug)
				System.err.println("Before optimization("+last+
						"): the dl = "+dl+" | best: "+minDL);

			if(dl < minDL)
				minDL = dl;  // The best dl so far	

			rst = rstats.getSimpleStats(last);	    
			if(m_Debug)
				System.err.println("The rule covers: "+rst[0]+
						" | pos = " + rst[2] + 
						" | neg = " + rst[4]+
						"\nThe rule doesn't cover: "+rst[1]+
						" | pos = " + rst[5]);

			stop = checkStop(rst, minDL, dl);

			if(!stop){	  		
				ruleset.addElement(oneRule);          // Accepted 
				newData = rstats.getFiltered(last)[1];// Data not covered
				hasPositive = Utils.gr(rst[5], 0.0);  // Positives remaining?
				if(m_Debug)
					System.err.println("One rule added: has positive? "
							+hasPositive);
			}
			else{
				if(m_Debug)
					System.err.println("Quit rule");
				rstats.removeLast(); // Remove last to be re-used
			}
		}// while !stop


		/******************** Optimization stage *******************/

		RuleStats finalRulesetStat = null; 
		for(int z=0; z < m_Optimizations; z++){
			if(m_Debug)
				System.err.println("\n*** Optimization: run #"
						+z+" ***");

			newData = data;		    
			finalRulesetStat = new RuleStats();
			finalRulesetStat.setData(newData);
			finalRulesetStat.setNumAllConds(m_Total);
			int position=0;
			stop = false;
			boolean isResidual = false;	    
			hasPositive = defHasPositive;		    
			dl = minDL = defDL;

			oneRule:    
				while(!stop && hasPositive){			

					isResidual = (position>=ruleset.size()); // Cover residual positive examples  
					// Re-do shuffling and stratification    
					//newData.randomize(m_Random);	
					newData = RuleStats.stratify(newData, m_Folds, m_Random);
					Instances[] part = RuleStats.partition(newData, m_Folds);
					growData=part[0];
					pruneData=part[1];
					//growData=newData.trainCV(m_Folds, m_Folds-1);
					//pruneData=newData.testCV(m_Folds, m_Folds-1);	   
					RipperRule finalRule;

					if(m_Debug)
						System.err.println("\nRule #"+position +
								"| isResidual?" + isResidual+
								"| data size: "+newData.sumOfWeights());

					if(isResidual){
						RipperRule newRule = new RipperRule(this.aprioriDistribution);   
						newRule.setConsequent(classIndex);
						if(m_Debug)
							System.err.println("\nGrowing and pruning"+
							" a new rule ...");
						newRule.grow(newData);
						finalRule = newRule;
						if(m_Debug)
							System.err.println("\nNew rule found: "+
									newRule.toString(m_Class));
					}
					else{
						RipperRule oldRule = (RipperRule)ruleset.elementAt(position);
						boolean covers = false;
						// Test coverage of the next old rule
						for(int i=0; i<newData.numInstances(); i++)
							if(oldRule.covers(newData.instance(i))){
								covers = true;
								break;
							}

						if(!covers){// Null coverage, no variants can be generated
							finalRulesetStat.addAndUpdate(oldRule);
							position++;
							continue oneRule;
						}  

						// 2 variants 
						if(m_Debug)
							System.err.println("\nGrowing and pruning"+
							" Replace ..."); 
						RipperRule replace = new RipperRule(this.aprioriDistribution);   
						replace.setConsequent(classIndex);
						replace.grow(growData);

						// Remove the pruning data covered by the following
						// rules, then simply compute the error rate of the
						// current rule to prune it.  According to Ripper,
						// it's equivalent to computing the error of the 
						// whole ruleset -- is it true?
						pruneData = RuleStats.rmCoveredBySuccessives(pruneData,ruleset, position);      	
						replace.prune(pruneData, true);

						if(m_Debug)
							System.err.println("\nGrowing and pruning"+
							" Revision ..."); 
						RipperRule revision = (RipperRule)oldRule.copy(); 

						// For revision, first rm the data covered by the old rule
						Instances newGrowData = new Instances(growData, 0);
						for(int b=0; b<growData.numInstances(); b++){
							Instance inst = growData.instance(b);
							if(revision.covers(inst))
								newGrowData.add(inst);
						}
						revision.grow(newGrowData);	      
						revision.prune(pruneData, true);

						double[][] prevRuleStats = new double[position][6];
						for(int c=0; c < position; c++)
							prevRuleStats[c] = finalRulesetStat.getSimpleStats(c);

						// Now compare the relative DL of variants
						FastVector tempRules = (FastVector)ruleset.copyElements();
						tempRules.setElementAt(replace, position);

						RuleStats repStat = new RuleStats(data, tempRules);
						repStat.setNumAllConds(m_Total);
						repStat.countData(position, newData, prevRuleStats);
						//repStat.countData();
						rst = repStat.getSimpleStats(position);	    
						if(m_Debug)
							System.err.println("Replace rule covers: "+rst[0]+
									" | pos = " + rst[2] + 
									" | neg = " + rst[4]+
									"\nThe rule doesn't cover: "+rst[1]+
									" | pos = " + rst[5]);

						double repDL = repStat.relativeDL(position, expFPRate,
								m_CheckErr);

						if(m_Debug)
							System.err.println("\nReplace: "+
									replace.toString(m_Class)
									+" |dl = "+repDL); 

						if(Double.isNaN(repDL) || Double.isInfinite(repDL))
							throw new Exception("Should never happen: repDL"+
									"in optmz. stage NaN or "+
							"infinite!");

						tempRules.setElementAt(revision, position);
						RuleStats revStat = new RuleStats(data, tempRules);
						revStat.setNumAllConds(m_Total);
						revStat.countData(position, newData, prevRuleStats);
						//revStat.countData();
						double revDL = revStat.relativeDL(position, expFPRate,
								m_CheckErr);

						if(m_Debug)
							System.err.println("Revision: "
									+ revision.toString(m_Class)
									+" |dl = "+revDL);

						if(Double.isNaN(revDL) || Double.isInfinite(revDL))
							throw new Exception("Should never happen: revDL"+
									"in optmz. stage NaN or "+
							"infinite!");

						rstats = new RuleStats(data, ruleset);
						rstats.setNumAllConds(m_Total);
						rstats.countData(position, newData, prevRuleStats);
						//rstats.countData();
						double oldDL = rstats.relativeDL(position, expFPRate,
								m_CheckErr);

						if(Double.isNaN(oldDL) || Double.isInfinite(oldDL))
							throw new Exception("Should never happen: oldDL"+
									"in optmz. stage NaN or "+
							"infinite!");
						if(m_Debug)
							System.err.println("Old rule: "+
									oldRule.toString(m_Class)
									+" |dl = "+oldDL); 

						if(m_Debug)
							System.err.println("\nrepDL: "+repDL+ 
									"\nrevDL: "+revDL+
									"\noldDL: "+oldDL);

						if((oldDL <= revDL) && (oldDL <= repDL))
							finalRule = oldRule; // Old the best
						else if(revDL <= repDL)
							finalRule = revision; // Revision the best
						else
							finalRule = replace; // Replace the best  
					}		

					finalRulesetStat.addAndUpdate(finalRule);  	 
					rst = finalRulesetStat.getSimpleStats(position);

					if(isResidual){	
						dl += finalRulesetStat.relativeDL(position, 
								expFPRate,
								m_CheckErr);

						if(m_Debug)
							System.err.println("After optimization: the dl"
									+"="+dl+" | best: "+minDL);

						if(dl < minDL)
							minDL = dl;  // The best dl so far

						stop = checkStop(rst, minDL, dl);
						if(!stop)
							ruleset.addElement(finalRule); // Accepted 
						else{
							finalRulesetStat.removeLast(); // Remove last to be re-used
							position--;
						}
					}
					else
						ruleset.setElementAt(finalRule, position); // Accepted 

					if(m_Debug){
						System.err.println("The rule covers: "+rst[0]+
								" | pos = " + rst[2] + 
								" | neg = " + rst[4]+
								"\nThe rule doesn't cover: "+rst[1]+
								" | pos = " + rst[5]);		
						System.err.println("\nRuleset so far: ");
						for(int x=0; x<ruleset.size(); x++)
							System.err.println(x+": "+((RipperRule)ruleset.elementAt(x)).toString(m_Class));
						System.err.println();
					}

					//Data not covered	
					if(finalRulesetStat.getRulesetSize() > 0)// If any rules	
						newData = finalRulesetStat.getFiltered(position)[1]; 
					hasPositive = Utils.gr(rst[5], 0.0); //Positives remaining? 
					position++;
				} // while !stop && hasPositive

			if(ruleset.size() > (position+1)){ // Hasn't gone through yet
				for(int k=position+1; k<ruleset.size(); k++)
					finalRulesetStat.addAndUpdate((Rule)ruleset.elementAt(k));
			}
			if(m_Debug)
				System.err.println("\nDeleting rules to decrease"+
				" DL of the whole ruleset ..."); 
			finalRulesetStat.reduceDL(expFPRate, m_CheckErr);

			if(m_Debug){
				int del = ruleset.size() -
				finalRulesetStat.getRulesetSize(); 
				System.err.println(del+" rules are deleted"+
				" after DL reduction procedure");
			}
			ruleset = finalRulesetStat.getRuleset();
			rstats = finalRulesetStat;	      	    

		} // For each run of optimization

		// Concatenate the ruleset for this class to the whole ruleset
		if(m_Debug){
			System.err.println("\nFinal ruleset: ");
			for(int x=0; x<ruleset.size(); x++)
				System.err.println(x+": "+((RipperRule)ruleset.elementAt(x)).toString(m_Class));
			System.err.println();
		}


		m_Ruleset.appendElements(ruleset);
		m_RulesetStats.addElement(rstats);

		return null;
	}   

	/**
	 * Check whether the stopping criterion meets
	 *
	 * @param rst the statistic of the ruleset
	 * @param minDL the min description length so far
	 * @param dl the current description length of the ruleset
	 * @return true if stop criterion meets, false otherwise
	 */
	private boolean checkStop(double[] rst, double minDL, double dl){


		if(dl > minDL+MAX_DL_SURPLUS){
			if(m_Debug)
				System.err.println("DL too large: "+dl+" | "+minDL);
			return true;
		}
		else 
			if(!Utils.gr(rst[2], 0.0)){// Covered positives
				if(m_Debug)
					System.err.println("Too few positives.");
				return true;
			}	
			else if((rst[4]/rst[0]) >= 0.5){// Err rate
				if(m_CheckErr){
					if(m_Debug)
						System.err.println("Error too large: "+
								rst[4] + "/" + rst[0]);
					return  true;
				}
				else
					return false;
			}		
			else{// Not stops
				if(m_Debug)
					System.err.println("Continue.");
				return  false;
			}				
	}

	/**
	 * Prints the all the rules of the rule learner.
	 *
	 * @return a textual description of the classifier
	 */
	public String toString() {
		if (m_Ruleset == null) 
			return "FURIA: No model built yet.";

		StringBuffer sb = new StringBuffer("FURIA rules:\n"+
		"===========\n\n"); 
		for(int j=0; j<m_RulesetStats.size(); j++){
			//System.out.println("ruleset stats = " + m_RulesetStats.size());
			RuleStats rs = (RuleStats)m_RulesetStats.elementAt(j);
			FastVector rules = rs.getRuleset();
			for(int k=0; k<rules.size(); k++){
				//System.out.println("rules size = " + rules.size());
				sb.append(((RipperRule)rules.elementAt(k)).toString(m_Class)
						+ " (CF = " + Math.round(100.0*((RipperRule)rules.elementAt(k)).getConfidence())/100.0 +")\n");
			}			    
		}

		sb.append("\n\n\nReglas Buenas\n");
		if(true){//m_Debug es lo que habia
			sb.append("Inside m_Ruleset\n");
			for(int i=0; i<m_Ruleset.size(); i++)
				sb.append(((RipperRule)m_Ruleset.elementAt(i)).toString(m_Class) + " (CF = " + Math.round(100.0*((RipperRule)m_Ruleset.elementAt(i)).getConfidence())/100.0 + ")\n");
		}
		sb.append("\nNumber of Rules : " 
				+ m_Ruleset.size() + "\n");




		return sb.toString();
	}

	//public static void main(String[] args) throws Exception {
	// runClassifier(new FURIA(), args);
	//}

    /**
     * Returns revision ( string "1.0")
     * @return  revision ( string "1.0")
     */
    
	public String getRevision() {
		return "1.0";
	}



}

